#include "imx_uart.h"
#include "linux/io.h"
#include "linux/string.h"
#include "linux/tty.h"
#include "linux/uart.h"
#include "linux/serial_core.h"
#include "linux/interrupt.h"
#include "linux/kernel.h"
#include "linux/common.h"
#include "linux/error.h"

#define CONFIG_UART_IMX6UL_COUNT 1
#define IMX6UL_UART_MAJOR 204
#define IMX6UL_UART_MINOR 64
#define IMX6UL_UART_TTY_NAME "ttySAC"

/*
 * @description : 初始化串口1所使用的IO引脚
 * @param		: 无
 * @return		: 无
 */
void uart_io_init(void)
{
	/* 1、初始化IO复用
	 * UART1_RXD -> UART1_TX_DATA
	 * UART1_TXD -> UART1_RX_DATA
	 */
	IOMUXC_SetPinMux(IOMUXC_UART1_TX_DATA_UART1_TX, 0); /* 复用为UART1_TX */
	IOMUXC_SetPinMux(IOMUXC_UART1_RX_DATA_UART1_RX, 0); /* 复用为UART1_RX */

	/* 2、配置UART1_TX_DATA、UART1_RX_DATA的IO属性
	 *bit 16:0 HYS关闭
	 *bit [15:14]: 00 默认100K下拉
	 *bit [13]: 0 keeper功能
	 *bit [12]: 1 pull/keeper使能
	 *bit [11]: 0 关闭开路输出
	 *bit [7:6]: 10 速度100Mhz
	 *bit [5:3]: 110 驱动能力R0/6
	 *bit [0]: 0 低转换率
	 */
	IOMUXC_SetPinConfig(IOMUXC_UART1_TX_DATA_UART1_TX, 0x10B0);
	IOMUXC_SetPinConfig(IOMUXC_UART1_RX_DATA_UART1_RX, 0x10B0);
}

/*
 * @description : 关闭指定的UART
 * @param - base: 要关闭的UART
 * @return		: 无
 */
void uart_disable(UART_Type *base)
{
	base->UCR1 &= ~(1 << 0);
}

/*
 * @description : 打开指定的UART
 * @param - base: 要打开的UART
 * @return		: 无
 */
void uart_enable(UART_Type *base)
{
	base->UCR1 |= (1 << 0);
}

/*
 * @description : 复位指定的UART
 * @param - base: 要复位的UART
 * @return		: 无
 */
void uart_softreset(UART_Type *base)
{
	base->UCR2 &= ~(1 << 0); /* UCR2的bit0为0，复位UART  	  	*/
	while ((base->UCR2 & 0x1) == 0)
		; /* 等待复位完成 					*/
}

/*
 * @description : 初始化串口1,波特率为115200
 * @param		: 无
 * @return		: 无
 */
void uartx_init(UART_Type *uart)
{
	/* 1、初始化串口IO 			*/
	//uart_io_init();

	/* 2、初始化UART1  			*/
	uart_disable(uart);	  /* 先关闭UART1 		*/
	uart_softreset(uart); /* 软件复位UART1 		*/

	uart->UCR1 = 0; /* 先清除UCR1寄存器 */

	/*
	 * 设置UART的UCR1寄存器，关闭自动波特率
	 * bit14: 0 关闭自动波特率检测,我们自己设置波特率
	 */
	uart->UCR1 &= ~(1 << 14);

	/*
	 * 设置UART的UCR2寄存器，设置内容包括字长，停止位，校验模式，关闭RTS硬件流控
	 * bit14: 1 忽略RTS引脚
	 * bit8: 0 关闭奇偶校验
	 * bit6: 0 1位停止位
	 * bit5: 1 8位数据位
	 * bit2: 1 打开发送
	 * bit1: 1 打开接收
	 */
	uart->UCR2 |= (1 << 14) | (1 << 5) | (1 << 2) | (1 << 1);

	/*
	 * UART1的UCR3寄存器
	 * bit2: 1 必须设置为1！参考IMX6ULL参考手册3624页
	 */
	uart->UCR3 |= 1 << 2;

	/*
	 * 设置波特率
	 * 波特率计算公式:Baud Rate = Ref Freq / (16 * (UBMR + 1)/(UBIR+1))
	 * 如果要设置波特率为115200，那么可以使用如下参数:
	 * Ref Freq = 80M 也就是寄存器UFCR的bit9:7=101, 表示1分频
	 * UBMR = 3124
	 * UBIR =  71
	 * 因此波特率= 80000000/(16 * (3124+1)/(71+1))=80000000/(16 * 3125/72) = (80000000*72) / (16*3125) = 115200
	 */
	uart->UFCR = 5 << 7; // ref freq等于ipg_clk/1=80Mhz
	uart->UBIR = 71;
	uart->UBMR = 3124;

	/* 使能串口 */
	uart_enable(uart);
}

/*
 * @description : 接收一个字符
 * @param 		: 无
 * @return		: 接收到的字符
 */
unsigned char getc(UART_Type *uart)
{
	while ((uart->USR2 & 0x1) == 0) /* 等待接收完成 */
		;

	return uart->URXD; /* 返回接收到的数据 */
}

char imx6ul_uart_putc(struct uart_port *port, unsigned char ch)
{
	UART_Type *uart;

	uart = (UART_Type *)(port->iobase);

	while (((uart->USR2 >> 3) & 0X01) == 0) /* 等待上一次发送完成 */
		;
	uart->UTXD = ch & 0XFF; /* 发送数据 */
	return ch;
}

static int imx6ul_uart_tty_write(struct tty *tty, const char *s, unsigned int count)
{
	int i;
	struct uart_port *port;

	if (!tty)
		return -ENODEV;

	port = (struct uart_port *)(tty->data);
	if (!port)
		return -ENODEV;

	for (i = 0; i < count; i++)
	{
		if (*(s + i) == '\n')
			imx6ul_uart_putc(port, '\r');
		imx6ul_uart_putc(port, *(s + i));
	}

	return 0;
}

static struct tty imx6ul_uart_tty1;
static struct uart_port imx6ul_uart1_port;
static struct uart_driver imx6ul_uart_drv;

int imx6ul_uart_init()
{
	void *vaddr;

	vaddr = early_ioremap(UART1_BASE, 0x100000);
	if (!vaddr)
		panic();

	uartx_init((UART_Type *)vaddr);

	imx6ul_uart_drv.dev_name = "imx6ul_uart";
	imx6ul_uart_drv.nr = 1,
	imx6ul_uart_drv.major = IMX6UL_UART_MAJOR;

	imx6ul_uart1_port.iobase = (unsigned long)vaddr;

	imx6ul_uart_tty1.name = IMX6UL_UART_TTY_NAME;
	imx6ul_uart_tty1.write = imx6ul_uart_tty_write;
	imx6ul_uart_tty1.index = 0;
	imx6ul_uart_tty1.data = &imx6ul_uart1_port;

	return register_tty(&imx6ul_uart_tty1);
}
